Học cách tổ chức code hiệu quả với Classes và Modules trong JavaScript
Object-Oriented Programming (OOP) là paradigm lập trình dựa trên khái niệm "objects" - các thực thể chứa data (properties) và code (methods). JavaScript hỗ trợ OOP thông qua:
Ý nghĩa: Gói gọn data và methods liên quan trong một object, ẩn chi tiết implementation.
Lợi ích:
Ý nghĩa: Class con có thể kế thừa properties và methods từ class cha.
Lợi ích:
Ý nghĩa: Cùng một method có thể hoạt động khác nhau ở các class khác nhau.
Lợi ích:
Ý nghĩa: Ẩn complexity, chỉ expose những gì cần thiết.
Lợi ích:
// 🕰️ ES5 và trước đó - Function Constructors
function PersonES5(name, age) {
this.name = name;
this.age = age;
}
PersonES5.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
var person1 = new PersonES5("John", 25);
// 🚀 ES6+ (2015) - Classes (Syntactic Sugar)
class PersonES6 {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
const person2 = new PersonES6("Jane", 30);
// 🔬 ES2022 - Private Fields
class PersonModern {
#age; // Private field
constructor(name, age) {
this.name = name;
this.#age = age;
}
get age() { return this.#age; }
greet() {
return `Hello, I'm ${this.name}`;
}
}
// 🕰️ 1. IIFE Pattern (Immediately Invoked Function Expression)
var MyModule = (function() {
var privateVar = "I'm private";
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
// 🕰️ 2. CommonJS (Node.js)
// math.js
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require('./math');
// 🕰️ 3. AMD (Asynchronous Module Definition)
define(['./math'], function(math) {
return {
calculate: function(a, b) {
return math.add(a, b);
}
};
});
// 🚀 4. ES6 Modules (ECMAScript 2015)
// math.js
export function add(a, b) { return a + b; }
// app.js
import { add } from './math.js';
| Aspect | Functions | Objects | Classes |
|---|---|---|---|
| Purpose | Logic execution | Data grouping | Blueprint for objects |
| Instantiation | Call function | Object literal | new ClassName() |
| Inheritance | No | Prototype chain | extends keyword |
| Private members | Closures | Symbols/_prefix | # prefix (ES2022) |
| Use cases | Utilities, helpers | Configuration, data | Complex entities |
// 📁 models/User.js - Class trong Module
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getProfile() {
return { name: this.name, email: this.email };
}
}
// 📁 services/UserService.js - Service Module sử dụng Class
import User from '../models/User.js';
import { validateEmail } from '../utils/validation.js';
export class UserService {
static users = [];
static createUser(name, email) {
if (!validateEmail(email)) {
throw new Error('Invalid email');
}
const user = new User(name, email);
this.users.push(user);
return user;
}
static getAllUsers() {
return this.users;
}
}
// 📁 app.js - Sử dụng cả Classes và Modules
import User from './models/User.js';
import { UserService } from './services/UserService.js';
// Sử dụng Class trực tiếp
const user1 = new User('John', 'john@example.com');
// Sử dụng qua Service
const user2 = UserService.createUser('Jane', 'jane@example.com');
console.log(UserService.getAllUsers());
Class là một template/blueprint (bản thiết kế) để tạo ra objects. Nó định nghĩa:
Class: Bản thiết kế (như bản vẽ nhà)
Object: Thực thể cụ thể (như ngôi nhà thật)
Thế giới thực → Lập trình:
class ClassName {
// 1. PROPERTIES (Thuộc tính)
// - Instance properties: khác nhau cho mỗi object
// - Static properties: thuộc về class, chung cho tất cả
// 2. CONSTRUCTOR (Hàm khởi tạo)
constructor(parameters) {
// Khởi tạo instance properties
this.property = value;
}
// 3. METHODS (Phương thức)
// - Instance methods: gọi từ object
// - Static methods: gọi từ class
// - Getters/Setters: truy cập như properties
// 4. PRIVATE FIELDS (ES2022)
// - Chỉ truy cập được từ bên trong class
#privateProperty;
#privateMethod() {
// Private method
}
}
| Aspect | Instance Members | Static Members |
|---|---|---|
| Ownership | Thuộc về object (instance) | Thuộc về class |
| Access | object.method() | ClassName.method() |
| this context | Refers to instance | Refers to class |
| Memory | Copy cho mỗi instance | Shared, chỉ 1 copy |
| Use cases | Object behavior, state | Utilities, factory methods |
Truy cập từ mọi nơi
this.property
method()
Chỉ trong class
this.#property
#method()
Convention: _property
this._property
_method()
Classes trong JavaScript là syntactic sugar cho prototype-based inheritance. Chúng cung cấp cách viết code OOP (Object-Oriented Programming) dễ đọc và dễ hiểu hơn, nhưng bên dưới vẫn sử dụng prototypes.
// ✅ Class declaration cơ bản
class Person {
// Constructor - hàm khởi tạo
constructor(name, age) {
this.name = name;
this.age = age;
this.id = Date.now(); // Tự động generate ID
}
// Instance methods - phương thức của đối tượng
greet() {
return `Hello, my name is ${this.name}`;
}
getAge() {
return this.age;
}
setAge(newAge) {
if (newAge > 0 && newAge < 150) {
this.age = newAge;
} else {
throw new Error('Invalid age');
}
}
// Getter - truy cập như property
get info() {
return `${this.name} (${this.age} years old)`;
}
// Setter - set giá trị như property
set fullName(value) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
}
// 🎯 Sử dụng class
const person1 = new Person('John Doe', 25);
const person2 = new Person('Jane Smith', 30);
console.log(person1.greet()); // "Hello, my name is John Doe"
console.log(person1.info); // "John Doe (25 years old)" - getter
person1.fullName = 'John Smith'; // setter
person1.setAge(26); // method call
// 🔍 Kiểm tra instance
console.log(person1 instanceof Person); // true
console.log(typeof person1); // "object"
class BankAccount {
// 🔒 Private fields (ES2022) - bắt đầu với #
#balance = 0;
#accountNumber;
// 📊 Static properties - thuộc về class, không phải instance
static totalAccounts = 0;
static bankName = 'JS Bank';
constructor(initialBalance = 0) {
this.#balance = initialBalance;
this.#accountNumber = this.#generateAccountNumber();
this.createdAt = new Date();
// Tăng số lượng accounts
BankAccount.totalAccounts++;
}
// 🔒 Private method
#generateAccountNumber() {
return Math.random().toString(36).substr(2, 9).toUpperCase();
}
// Public methods
deposit(amount) {
if (amount <= 0) {
throw new Error('Amount must be positive');
}
this.#balance += amount;
return this.#balance;
}
withdraw(amount) {
if (amount <= 0) {
throw new Error('Amount must be positive');
}
if (amount > this.#balance) {
throw new Error('Insufficient funds');
}
this.#balance -= amount;
return this.#balance;
}
// Getter cho private field
get balance() {
return this.#balance;
}
get accountNumber() {
return this.#accountNumber;
}
// 🏭 Static methods - gọi trực tiếp từ class
static createSavingsAccount(initialBalance) {
const account = new BankAccount(initialBalance);
account.type = 'savings';
account.interestRate = 0.02;
return account;
}
static getTotalAccounts() {
return `${BankAccount.bankName} has ${BankAccount.totalAccounts} accounts`;
}
}
// 🎯 Sử dụng
const account1 = new BankAccount(1000);
const account2 = BankAccount.createSavingsAccount(5000);
console.log(account1.balance); // 1000
console.log(account1.accountNumber); // "XYZ123ABC"
account1.deposit(500);
console.log(account1.balance); // 1500
// ❌ Không thể truy cập private fields
// console.log(account1.#balance); // SyntaxError
// 🏭 Static methods
console.log(BankAccount.getTotalAccounts()); // "JS Bank has 2 accounts"
// 👨🎓 Base class (parent)
class Employee {
constructor(name, salary) {
this.name = name;
this.salary = salary;
this.startDate = new Date();
}
work() {
return `${this.name} is working`;
}
getAnnualSalary() {
return this.salary * 12;
}
getInfo() {
return `Employee: ${this.name}, Salary: $${this.salary}`;
}
}
// 👨💻 Derived class (child) - kế thừa từ Employee
class Developer extends Employee {
constructor(name, salary, programmingLanguage) {
// Gọi constructor của parent class
super(name, salary);
this.programmingLanguage = programmingLanguage;
this.projects = [];
}
// Override method từ parent
work() {
return `${this.name} is coding in ${this.programmingLanguage}`;
}
// Method riêng của Developer
addProject(project) {
this.projects.push(project);
}
// Override getInfo với thêm thông tin
getInfo() {
const baseInfo = super.getInfo(); // Gọi method của parent
return `${baseInfo}, Language: ${this.programmingLanguage}`;
}
}
// 👨💼 Another derived class
class Manager extends Employee {
constructor(name, salary, department) {
super(name, salary);
this.department = department;
this.team = [];
}
work() {
return `${this.name} is managing ${this.department} department`;
}
addTeamMember(employee) {
this.team.push(employee);
}
getTeamSize() {
return this.team.length;
}
getInfo() {
const baseInfo = super.getInfo();
return `${baseInfo}, Department: ${this.department}, Team: ${this.getTeamSize()}`;
}
}
// 🎯 Sử dụng inheritance
const dev = new Developer('Alice Johnson', 75000, 'JavaScript');
const manager = new Manager('Bob Smith', 90000, 'Engineering');
console.log(dev.work()); // "Alice Johnson is coding in JavaScript"
console.log(manager.work()); // "Bob Smith is managing Engineering department"
dev.addProject('E-commerce website');
manager.addTeamMember(dev);
console.log(dev.getInfo());
// "Employee: Alice Johnson, Salary: $75000, Language: JavaScript"
console.log(manager.getInfo());
// "Employee: Bob Smith, Salary: $90000, Department: Engineering, Team: 1"
// 🔍 Kiểm tra inheritance
console.log(dev instanceof Developer); // true
console.log(dev instanceof Employee); // true
console.log(dev instanceof Object); // true
Module là một đơn vị code độc lập, có thể tái sử dụng, chứa:
Mỗi module có scope riêng và interface rõ ràng (export/import).
Mỗi module chỉ làm một việc và làm tốt việc đó.
Modules ít phụ thuộc lẫn nhau, dễ thay đổi.
Code trong module liên quan chặt chẽ với nhau.
Ẩn implementation details, chỉ expose cần thiết.
| Strategy | When | How | Benefits |
|---|---|---|---|
| Static Import | Build time | import statement | Tree shaking, optimization |
| Dynamic Import | Runtime | import() function | Code splitting, lazy loading |
| Conditional Import | Based on conditions | if + import() | Feature-based loading |
| Lazy Import | When needed | Event-driven import() | Better initial performance |
// 📤 1. NAMED EXPORTS - Multiple exports
export const API_URL = 'https://api.example.com';
export function get(url) { /* ... */ }
export function post(url, data) { /* ... */ }
export class ApiClient { /* ... */ }
// 📤 2. DEFAULT EXPORT - Main export
export default class User {
constructor(name) { this.name = name; }
}
// 📤 3. MIXED EXPORTS - Default + Named
export default function calculate(a, b) { return a + b; }
export const PI = 3.14159;
export const E = 2.71828;
// 📤 4. RE-EXPORTS - Barrel pattern
export { User } from './User.js';
export { Product } from './Product.js';
export { Order } from './Order.js';
// 📤 5. DYNAMIC EXPORTS - Conditional
const utils = {};
if (process.env.NODE_ENV === 'development') {
utils.debug = function() { /* debug code */ };
}
export default utils;
// 📥 1. NAMED IMPORTS - Specific items
import { get, post, API_URL } from './api.js';
// 📥 2. DEFAULT IMPORT - Main item
import User from './User.js';
// 📥 3. NAMESPACE IMPORT - All as object
import * as API from './api.js';
// Usage: API.get(), API.post(), API.API_URL
// 📥 4. MIXED IMPORT - Default + Named
import User, { USER_ROLES, createUser } from './User.js';
// 📥 5. ALIASED IMPORT - Rename imports
import { get as apiGet, post as apiPost } from './api.js';
// 📥 6. SIDE-EFFECT IMPORT - Just execute
import './polyfills.js'; // No variables imported
// 📥 7. DYNAMIC IMPORT - Runtime loading
const userModule = await import('./User.js');
const User = userModule.default;
// 📥 8. CONDITIONAL IMPORT
if (window.innerWidth < 768) {
const mobileUtils = await import('./mobile-utils.js');
mobileUtils.setupMobileUI();
}
Single entry point che giấu complexity
Module exports factory functions
Extensible module system
Business logic trong services
Modules cho phép chia code thành các file riêng biệt, mỗi file có scope riêng. Điều này giúp:
// mathUtils.js - Module xuất nhiều functions
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
// Export class
export class Calculator {
constructor() {
this.history = [];
}
calculate(operation, a, b) {
let result;
switch(operation) {
case 'add': result = add(a, b); break;
case 'subtract': result = subtract(a, b); break;
case 'multiply': result = multiply(a, b); break;
case 'divide': result = divide(a, b); break;
default: throw new Error('Unknown operation');
}
this.history.push({operation, a, b, result});
return result;
}
getHistory() {
return this.history;
}
}
// Export object
export const mathConstants = {
PI: 3.14159,
E: 2.71828,
GOLDEN_RATIO: 1.61803
};
// main.js - Các cách import
// 1. Named imports - import specific exports
import { add, subtract, PI } from './mathUtils.js';
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
console.log(PI); // 3.14159
// 2. Import with alias - đổi tên
import { multiply as mul, divide as div } from './mathUtils.js';
console.log(mul(4, 5)); // 20
console.log(div(20, 4)); // 5
// 3. Import all - import tất cả thành namespace
import * as MathUtils from './mathUtils.js';
console.log(MathUtils.add(1, 2)); // 3
console.log(MathUtils.PI); // 3.14159
const calc = new MathUtils.Calculator();
// 4. Import class
import { Calculator } from './mathUtils.js';
const calculator = new Calculator();
calculator.calculate('add', 10, 5);
calculator.calculate('multiply', 3, 4);
console.log(calculator.getHistory());
// 5. Mixed imports
import { mathConstants, Calculator as Calc } from './mathUtils.js';
console.log(mathConstants.E); // 2.71828
const myCalc = new Calc();
// User.js - Default export với class
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.id = this.generateId();
this.createdAt = new Date();
}
generateId() {
return Math.random().toString(36).substr(2, 9);
}
getProfile() {
return {
id: this.id,
name: this.name,
email: this.email,
memberSince: this.createdAt.getFullYear()
};
}
updateEmail(newEmail) {
if (this.isValidEmail(newEmail)) {
this.email = newEmail;
return true;
}
return false;
}
isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// Named exports cùng với default
export const USER_ROLES = {
ADMIN: 'admin',
USER: 'user',
MODERATOR: 'moderator'
};
export function createUser(name, email, role = USER_ROLES.USER) {
const user = new User(name, email);
user.role = role;
return user;
}
// app.js - Import default và named exports
// Import default - có thể đặt tên bất kỳ
import User from './User.js';
import MyUser from './User.js'; // Cùng module, tên khác
// Import default + named trong cùng 1 dòng
import User, { USER_ROLES, createUser } from './User.js';
// Sử dụng
const user1 = new User('John Doe', 'john@example.com');
const user2 = createUser('Jane Smith', 'jane@example.com', USER_ROLES.ADMIN);
console.log(user1.getProfile());
console.log(user2.role); // 'admin'
// ✅ Default export examples
// config.js
export default {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
// utils.js
export default function formatDate(date) {
return date.toLocaleDateString('vi-VN');
}
// constants.js
export default Object.freeze({
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
ALLOWED_TYPES: ['image/jpeg', 'image/png'],
DEFAULT_LANGUAGE: 'vi'
});
// Dynamic imports - import modules khi cần thiết
// 1. Basic dynamic import
async function loadMathUtils() {
try {
const mathModule = await import('./mathUtils.js');
// Sử dụng named exports
console.log(mathModule.add(5, 3));
console.log(mathModule.PI);
// Sử dụng class
const calc = new mathModule.Calculator();
return calc;
} catch (error) {
console.error('Failed to load math utils:', error);
}
}
// 2. Conditional loading
async function loadUserModule(userType) {
let UserClass;
if (userType === 'admin') {
const module = await import('./AdminUser.js');
UserClass = module.default;
} else {
const module = await import('./RegularUser.js');
UserClass = module.default;
}
return new UserClass();
}
// 3. Lazy loading với destructuring
async function calculateAdvanced(operation, a, b) {
// Chỉ load module khi cần advanced calculations
const { advancedMath } = await import('./advancedMath.js');
return advancedMath[operation](a, b);
}
// 4. Dynamic import với Promise.all
async function loadMultipleModules() {
try {
const [mathModule, userModule, utilsModule] = await Promise.all([
import('./mathUtils.js'),
import('./User.js'),
import('./utils.js')
]);
return {
math: mathModule,
User: userModule.default,
utils: utilsModule
};
} catch (error) {
console.error('Failed to load modules:', error);
}
}
// 5. Dynamic import với error handling
function loadModuleWithRetry(modulePath, maxRetries = 3) {
return new Promise(async (resolve, reject) => {
for (let i = 0; i < maxRetries; i++) {
try {
const module = await import(modulePath);
resolve(module);
return;
} catch (error) {
if (i === maxRetries - 1) {
reject(error);
}
// Wait before retry
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
});
}
// 6. Feature detection + dynamic import
async function loadFeatureModule(featureName) {
// Kiểm tra browser support
if ('serviceWorker' in navigator && featureName === 'offline') {
const module = await import('./offlineFeature.js');
return module.default;
}
if ('geolocation' in navigator && featureName === 'location') {
const module = await import('./locationFeature.js');
return module.default;
}
throw new Error(`Feature ${featureName} not supported`);
}
// Product.js - Product class
export default class Product {
#price;
#stock;
constructor(name, price, category) {
this.id = this.generateId();
this.name = name;
this.#price = price;
this.category = category;
this.#stock = 0;
this.createdAt = new Date();
}
generateId() {
return `prod_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
}
get price() { return this.#price; }
get stock() { return this.#stock; }
setPrice(newPrice) {
if (newPrice < 0) throw new Error('Price cannot be negative');
this.#price = newPrice;
}
addStock(quantity) {
if (quantity < 0) throw new Error('Quantity cannot be negative');
this.#stock += quantity;
}
reduceStock(quantity) {
if (quantity > this.#stock) throw new Error('Insufficient stock');
this.#stock -= quantity;
}
}
// Cart.js - Shopping cart
import Product from './Product.js';
export default class ShoppingCart {
constructor() {
this.items = new Map();
this.discounts = [];
}
addItem(product, quantity = 1) {
if (!(product instanceof Product)) {
throw new Error('Invalid product');
}
if (product.stock < quantity) {
throw new Error('Insufficient stock');
}
const existingQuantity = this.items.get(product.id) || 0;
this.items.set(product.id, existingQuantity + quantity);
// Reduce stock
product.reduceStock(quantity);
}
removeItem(productId) {
this.items.delete(productId);
}
getTotal() {
let total = 0;
this.items.forEach((quantity, productId) => {
const product = this.findProduct(productId);
if (product) {
total += product.price * quantity;
}
});
return this.applyDiscounts(total);
}
applyDiscounts(total) {
return this.discounts.reduce((acc, discount) => {
return discount.apply(acc);
}, total);
}
}
// app.js - Main application
async function main() {
// Dynamic import cho admin features
const isAdmin = await checkUserRole();
if (isAdmin) {
const { AdminPanel } = await import('./admin/AdminPanel.js');
const adminPanel = new AdminPanel();
adminPanel.init();
}
// Regular e-commerce functionality
const { default: Product } = await import('./Product.js');
const { default: ShoppingCart } = await import('./Cart.js');
const laptop = new Product('MacBook Pro', 2000, 'Electronics');
const cart = new ShoppingCart();
cart.addItem(laptop, 1);
console.log('Total:', cart.getTotal());
}
Yêu cầu: Tạo hệ thống quản lý thư viện với classes Book, Author, và Library.
Yêu cầu: Tạo hệ thống calculator sử dụng modules, với các file riêng cho basic operations, advanced operations, và UI.
Yêu cầu: Tạo platform e-commerce hoàn chỉnh với inheritance, modules, và dynamic loading.